CMU 15619 Cloud Computing 的 team project,拖了很久,最终还是鼓起勇气整理了。时隔三个多月,回头来看,找到了更多可以优化的点,本篇内容许多是和同伴讨论整理后得出,借鉴了小土刀的博客,然而现在找不到具体地址了抱歉。
项目介绍
简介
搭建性能高、可靠性好的 web 服务,前端负责处理请求,处理较高负载(大约每秒至少30000次请求)
- 数据预处理:对大数据集(约1TB)进行预处理,包括过滤脏数据、敏感词、处理停顿词、计算情感分析权重值等,在 hadoop 平台上实现ETL;
- 后端:保存清理后的 twitter 数据,评估 SQL (MySQL) 和 NoSQL (HBase) 在不同类型的数据、不同大小的数据集下的 performance。
- 前端:接收并响应不同类型的 HTTP GET 请求
- 要求:给定预算,最优化性能。
四种 query 类型
Query1
要求: 对加密信息进行破译,返回正确信息。
难度系数: 低。
这一阶段主要是用来熟悉各种 web 框架,包括 Undertow, Vert.X, Netty 等,比较性能选定合适框架。
Request:
GET /q1?key=&message=
Response:
TEAMID,TEAM_AWS_ACCOUNT_ID\n yyyy-MM-dd HH:mm:ss\n [The decrypted message M]\n
Query2
要求: 处理大量的读请求。
难度系数: 高。
对数据格式有较高要求,如何处理不同语言,各种特殊符号,如何处理敏感词、停顿词、计算情感分析权重值、过滤脏数据等。
对性能有较高要求,如何设计前后端来处理高并发的读请求。
Request:
GET /q2?userid=uid&hashtag=hashtag
Response:
TEAMID,TEAM_AWS_ACCOUNT_ID\n Sentiment_density1:Tweet_time1:Tweet_id1:Censored_text1\n Sentiment_density2:Tweet_time2:Tweet_id2:Censored_text2\n Sentiment_density3:Tweet_time3:Tweet_id3:Censored_text3\n ...
Query3
要求: 给定一定范围的 user id 和 日期,计算关键词出现的次数。处理大量的写请求。
难度系数: 中。
依旧是性能的要求。
Request:
GET/q3?start_date=yyyy-mm-dd&end_date=yyyy-mm-dd&start_userid=uid&end_userid=uid&words=w1,w2,w3
Response:
TEAMID,TEAM_AWS_ACCOUNT_ID\n w1:count1\n w2:count2\n w3:count3\n
Query4
要求: 处理高并发读写请求,有一致性要求。
难度系数: 高。
Request:
GET:
q4?tweetid=&op=set&seq= &fields= &payload=
SET:
q4?tweetid=&op=set&seq= &fields= &payload=
Response:
GET:
q4?tweetid=&op=get&seq= &fields= &payload=
SET:
TEAMID,TEAM_AWS_ACCOUNT_ID\n success\n
应用场景
这篇博客是针对 query 2 进行的反思,进一步明确应用场景。
- 数据是 5100W 条左右带 tag 的 tweet
- 只有读请求,每次需要返回指定用户用指定 tag 发送的 tweet
- 有一定的预算限制(不能任意开机器来凑性能)
- 前端使用 Undertow,后端是部署在 Amazon 的 MySQL 和 Hbase
充分理解应用场景非常非常非常重要,至少需要明确服务具体接收的请求的格式和具体需要返回的内容是什么;是偏向读还是偏向写,还是读写比较均衡;数据量大概是多少…之后才能进行针对性的优化设计。
通用优化
纵观整个 Request-response 流程,分以下几个步骤:
a. Load Generator to Load Balancer (if any, else merge with b.)
b. Load Balancer to Web Service
c. Parsing request
d. Web Service to DB
e. At DB (execution)
f. DB to Web Service
g. Parsing DB response
h. Web Service to LB
i. LB to LG
i 部分我们不需要考虑,因为这个场景并没有用任何渲染引擎,就是单纯返回一段数据而已;c 部分设涉及了解密算法,优化从代码层面入手。数据库(e)与网络传输(b、d、f、h)部分,是主要的瓶颈所在,我们一点一点来分析。
网络传输优化
b、d、f、h 部分,实际就是网络部分优化,b、h 涉及 front-end,d、f 涉及 back-end。对后端而言,因为是 只读操作,所以不需要考虑一致性问题,可以做的是
努力增加并发数
- 使用 ELB 增加多台前端,多台机器并发请求;
- 同理,多台后端分发数据库请求。
- 每台机器增加线程数(当然要在内存的允许范围内),但是加到一定程度也就足够了(毕竟带宽是有限的);
减少每次传输所需要的带宽
如在后端对数据进行压缩,在前端进行解压缩,以减少数据传输所需带宽。
这一部分优化的另一个方向是设计缓存,减少对后台的请求。这里的缓存不是说数据库的缓存查询,而是前端对 response 的缓存,目的是对一些请求不用查询数据库就能返回 response,采用的方式通常是 temporal and spatial locality。
有缓存,那么肯定就有预热,预热的重要性在于,把常用的记录缓存下来,为了多一些的缓存,可以是开一个内存优化的机器,比其他系列多一倍内存。
对于 g 的优化,很简单,联系业务场景,读比较多,因此可以对数据进行预处理后(整理成 response 的格式)再存入数据库,用空间换时间。
数据库优化
MySQL
选择合适的存储引擎
在 MySQL 中有两个存储引擎 MyISAM 和 InnoDB,每个引擎都有利有弊。
MyISAM 偏好读操作。适合于需要大量查询的应用,对于有大量写操作并不是很好。update一个字段,整个表都会被锁起来,而其他进程包括读进程都无法操作。MyISAM 对于SELECT COUNT(*) 这类的计算是超快无比的。
InnoDB 偏好写操作。是一个非常复杂的存储引擎,对于一些小的应用,它会比 MyISAM 还慢。他是它支持“行锁” ,于是在写操作比较多的时候,会更优秀。并且,他还支持更多的高级应用,比如:事务。sharding and replication
sharding 是对数据分区存储在不同数据库里,需要考虑的是怎么分区,需要设定规则,可能导致的问题是请求不均衡;
replication 比较简单,同一份数据多备份几分,最简单采用 round-robin 分配请求就可以了。建立 index
根据使用频率决定哪些字段需要建立索引,选择经常作为连接条件、筛选条件、聚合查询、排序的字段作为索引的候选字段。字段设计
尽量不要允许 NULL,除非必要,可以用 NOT NULL+DEFAULT 代替。
少用TEXT和IMAGE,二进制字段的读写是比较慢的,而且,读取的方法也不多,大部分情况下最好不用。
另外,mysql有一个analyse query的功能,可以来帮助你加快速度,比如数据类型的调整,不过这是建立在数据量很大的情况下查询语句
横向来看,不要写 SELECT *的语句,而是选择需要的字段;纵向来看,合理写 WHERE 子句,不要写没有WHERE的SQL语句;
开启查询缓存;参数配置
配置文件 /etc/my.cnfmax_connections 默认的151,可以修改为3000(750M)
max_connections 是指 MySql 的最大连接数,如果服务器的并发连接请求量比较大,建议调高此值,以增加并行连接数量,当然这建立在机器能支撑的情况下,因为如果连接数越多,介于MySql会为每个连接提供连接缓冲区,就会开销越多的内存,所以要适当调整该值,不能盲目提高设值。可以过’conn%’通配符查看当前状态的连接数量,以定夺该值的大小。MySQL服务器允许的最大连接数16384;查看系统当前最大连接数 show variables like ‘max_connections’;thread_concurrency 应该设定为CPU核数的2倍
thread_concurrency 的值的正确与否, 对mysql的性能影响很大, 在多个cpu(或多核)的情况下,错误设置了 thread_concurrency 的值, 会导致mysql不能充分利用多cpu(或多核), 出现同一时刻只能一个cpu(或核)在工作的情况。back_log 默认的50,可以修改为500.(每个连接256kb,占用:125M)
back_log 值指出在 MySQL 暂时停止回答新请求之前的短时间内多少个请求可以被存在堆栈中。也就是说,如果MySql的连接数据达到 max_connections 时,新来的请求将会被存在堆栈中,以等待某一连接释放资源,该堆栈的数量即back_log,如果等待连接的数量超过 back_log,将不被授予连接资源。
back_log 值不能超过TCP/IP连接的侦听队列的大小。若超过则无效,查看当前系统的TCP/IP连接的侦听队列的大小命令:cat /proc/sys/net/ipv4/tcp_max_syn_backlog。对于 Linux 系统推荐设置为小于 512 的整数。
show variables like ‘back_log’; 查看当前数量
HBase
Rowkey 设计
Rowkey 一定要尽量短 (如:时间用时间戳整数表示、编码压缩)key-value 的设计
把一些重要的筛选信息左移到合适的位置,从而在不改变数据量的情况下,提高查询性能,尽量把查询维度或信息存储在行健中,因为它筛选数据的效率最高。
理由:HBase 的 Rowkey 是数据行的唯一标识,必须通过它进行数据行访问,目前有三种方式,单行键访问、行键范围访问、全表扫描访问。数据按行键的方式排序存储,依次按位比较,数值较大的排列在后,例如 int 方式的排序:1,10,100,11,12,2,20…,906,…。增加数据节点
Hbase 在行方向上水平划分成 N 个 Region,每个表一开始只有一个 Region,数据量增多,Region 自动分裂为两个,不同 Region 分布在不同 Server 上,但同一个不会拆分到不同 Server。一个 region 只能由一个服务器管理,所以总是添加到同一个 region 上,会造成读写热点,从而使集群性能下降。解决方法,比如我们有9台服务器,那么我们就把0-9均匀加到行健前缀,这样就会被平均的分到不同的 region 服务器上了,好处是,因为相连的数据都分布到不同的服务器上了,用户可以多线程并行的读取数据,这样查询的吞吐量会提高。参数配置
分配合适的内存给 RegionServer 服务
在不影响其他服务的情况下,越大越好。例如在 HBase 的 conf 目录下的 hbase-env.sh 的最后添加 export HBASE_REGIONSERVER_OPTS=”-Xmx16000m $HBASE_REGIONSERVER_OPTS”
其中 16000m 为分配给 RegionServer 的内存大小。RegionServer 的请求处理 IO 线程数
较少的 IO 线程适用于处理单次请求内存消耗较高的 Big Put 场景 (大容量单次 Put 或设置了较大 cache 的 Scan,均属于 Big Put) 或 ReigonServer 的内存比较紧张的场景。
较多的 IO 线程,适用于单次请求内存消耗低,TPS 要求 (每秒事务处理量 (TransactionPerSecond)) 非常高的场景。设置该值的时候,以监控内存为主要参考。
在 hbase-site.xml 配置文件中配置项为 hbase.regionserver.handler.count。
200调整 Block Cache
hfile.block.cache.size:RS的block cache的内存大小限制,默认值0.25,在偏向读的业务中,可以适当调大该值,具体配置时需试hbase集群服务的业务特征,结合memstore的内存占比进行综合考虑。
进一步优化 – 架构设计
上一部分的优化并不能解决所有问题,至少不能解决多少 front-end 机器,多少 back-end 机器。进一步的优化更多的是依靠实验、依靠监控和日志分析。
场景
给定预算,优化性能。
一般思路:auto-scaling 方式,在高峰期增加机器,低谷期减少机器
困难
模拟 2 分钟 = 现实 1 小时 –> 30 倍缩放
现实 2 小时的高峰期对应只有 4 分钟,然而坑爹的是!!!aws 申请机器到使用,有 3-5 分钟的延迟,这个延迟并不会被缩放,也就是说你看到高峰立即响应、增加机器,等到机器投入使用了,高峰就过了。。。
所以,要么提前 3-5 分钟预测到高峰期,要么,简单粗暴开够机器,等着。
怎么预测?可以通过观察监控数据,拟合流量曲线。然而设想是美好的,现实是残酷的。模拟实验有时会让服务器假装『挂掉』,这样有一段时间就无法处理任何请求,所以……所以难以拟合。
能做的,只有设定 baseline,调整参数(前后端各有几台机器),不断实验,找最优解了,这里的原则是 充分利用硬件资源。 在预算条件下,对每台机器,其 CPU,内存,带宽等资源都要尽可能的使用,如果资源不平衡,就说明钱没有花在刀刃上,可以考虑更换不同类型的机器,Amazon 提供了『通用』,『内存优化』和『计算优化』这几种不同的机器,可以根据监控的数据,根据前后端不同的任务来决定具体使用什么类型的机器。
监控
怎么调整参数?答案就是看监控。
重要指标:
CPU / 内存 / 网络
从这三个角度的数据观察规律,或者用 aws 的 cloud watch 设定一些阈值,设置自动提醒,当然也可以自己写脚本,省钱!
日志
一般来说,不同的 web 服务,用户的请求模式总体来说是有一定规律的。对于 Twitter 数据的分析,就有热门/冷门的用户/hashtag/单词/时间段(比方说有重大事件发生的日子,tweet 的数量可能会更多)
我们应该根据具体的需求,通过统计大致了解数据分布。比方说其中一个请求是返回某用户包含某 hashtag 的 tweet,那么我们最好需要了解哪些用户热门,哪些 hashtag 热门,然后根据这些特点来设计数据库结构、设计缓存。就 Hbase 而言,可以根据这些日志,进行数据库中不同 region 在不同 regionserver 的平衡,充分利用 HBase 的能力。
设计举例
前端使用 ELB(负载均衡) + 2 台机器,后端使用 1(master) + 3(slave) 的模式可能是最科学的,这样可以尽可能得减轻前端单机压力。